use std::io::Write;
use std::{io,thread};
use std::sync::mpsc;
use std::time::{Duration,Instant};
use crate::game;
use game::{Piece,Loc};

const HEAD_HEARTBEAT:&str                   = "HEARTBEAT";
const MESSAGE_SPLIT_0:&str                  = " ";
const MESSAGE_SPLIT_1:&str                  = "|";
const MESSAGE_SPLIT_POINTSTRING:&str        = ",";

const EU_HEAD_READY:&str                    = "READY";
const EU_HEAD_MOVE:&str                     = "MOVE";
const EU_HEAD_IN_POINTS:&str                = "IN-POINTS";
const EU_HEAD_STATUS:&str                   = "STATUS";
const EU_HEAD_SAY:&str                      = "SAY";
const EU_HEAD_CONF:&str                     = "CONF";
const EU_HEAD_CRASH:&str                    = "CRASH";
const EU_LOC_TEXTS:[&str; game::BOARD_LEN]  = [
    "A15","B15","C15","D15","E15","F15","G15","H15","I15","J15","K15","L15","M15","N15","O15",
    "A14","B14","C14","D14","E14","F14","G14","H14","I14","J14","K14","L14","M14","N14","O14",
    "A13","B13","C13","D13","E13","F13","G13","H13","I13","J13","K13","L13","M13","N13","O13",
    "A12","B12","C12","D12","E12","F12","G12","H12","I12","J12","K12","L12","M12","N12","O12",
    "A11","B11","C11","D11","E11","F11","G11","H11","I11","J11","K11","L11","M11","N11","O11",
    "A10","B10","C10","D10","E10","F10","G10","H10","I10","J10","K10","L10","M10","N10","O10",
    "A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9", "I9", "J9", "K9", "L9", "M9", "N9", "O9",
    "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8", "J8", "K8", "L8", "M8", "N8", "O8",
    "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "I7", "J7", "K7", "L7", "M7", "N7", "O7",
    "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "I6", "J6", "K6", "L6", "M6", "N6", "O6",
    "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "I5", "J5", "K5", "L5", "M5", "N5", "O5",
    "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4", "J4", "K4", "L4", "M4", "N4", "O4",
    "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "I3", "J3", "K3", "L3", "M3", "N3", "O3",
    "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2", "J2", "K2", "L2", "M2", "N2", "O2",
    "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1", "K1", "L1", "M1", "N1", "O1",
];

const UE_HEAD_RAII:&str                     = "RAII";
const UE_HEAD_GET:&str                      = "GET";
const UE_HEAD_SET_BOARD:&str                = "SET-BOARD";
const UE_HEAD_SET_RULE:&str                 = "SET-RULE";
const UE_HEAD_GIVE_POINTS:&str              = "GIVE-POINTS";
const UE_HEAD_SELECT_POINT:&str             = "SELECT-POINT";
const UE_HEAD_SET_CONF:&str                 = "SET-CONF";
const UE_HEAD_RESET:&str                    = "RESET";

const TIMELIMIT_MIN_SECOND:u64 = 3;
const TIMELIMIT_MAX_SECOND:u64 = 2147483648;
const HEARTBETA_TAKE_TIMEOUT:Duration = Duration::from_millis(6000);
const HEARTBEAT_SEND_TIMEOUT:Duration = Duration::from_millis(4100);
const SESSION_FRAME_SPAC_TIME:Duration = Duration::from_millis(50);

// Processer Define for Ux->Engine
pub trait Receiver {
    // UE: GET {P} {T} {ID}
    fn get(&mut self,  p:Piece, t:Duration, id:&str);
    // UE: SET-BOARD {Bs}|{Ws}|{Last}
    fn set_board(&mut self, black_locs:&[Loc], white_locs:&[Loc], lastmove_loc:Option<Loc>);
    // UE: SET-RULE forbidden {Value}
    fn set_rule_forbidden(&mut self, use_forbidden:bool);
    // UE: SET-RULE canswaps {Value}
    fn set_rule_canswaps(&mut self, insteps:&[usize]);
    // UE: GIVE-POINTS {N} {ID}
    fn give_points(&mut self, point_nums:usize, id:&str);
    // UE: SELECT-POINT {Ls} {ID}
    fn select_point(&mut self, point_locs:&[Loc], id:&str);
    // UE: SET-CONF {ID} {A}
    fn set_conf(&mut self, id:&str, raw_value:&str);
    // UE: RESET
    fn reset(&mut self);
    // Take RAII message from Ux, or HEARTBEAT message timeout with from Ux
    fn _raii(&mut self);
    // Take a unknown message
    fn _others(&mut self, raw:&str);
}

fn parse_ue_piece(text:&str) -> Piece {
    match text {
        "B" => {game::P_BLACK}
        "b" => {game::P_BLACK}
        "W" => {game::P_WHITE}
        "w" => {game::P_WHITE}
        _ => {game::P_EMPTY}
    }
}

fn parse_ue_point(text:&str) -> Loc {
    if text.len() > 1 {
        let first = &text[0..1];
        let rest = &text[1..text.len()];
        let h = match first {
            "A" => {0} "a" => {0}
            "B" => {1} "b" => {1}
            "C" => {2} "c" => {2}
            "D" => {3} "d" => {3}
            "E" => {4} "e" => {4}
            "F" => {5} "f" => {5}
            "G" => {6} "g" => {6}
            "H" => {7} "h" => {7}
            "I" => {8} "i" => {8}
            "J" => {9} "j" => {9}
            "K" => {10} "k" => {10}
            "L" => {11} "l" => {11}
            "M" => {12} "m" => {12}
            "N" => {13} "n" => {13}
            "O" => {14} "o" => {14}
            _ => {Loc::MAX}
        };
        let v = match rest {
            "15" => {0}
            "14" => {1}
            "13" => {2}
            "12" => {3}
            "11" => {4}
            "10" => {5}
            "9" => {6}
            "8" => {7}
            "7" => {8}
            "6" => {9}
            "5" => {10}
            "4" => {11}
            "3" => {12}
            "2" => {13}
            "1" => {14}
            _ => {Loc::MAX}
        };
        if (h < game::BOARD_SIZE) && (v < game::BOARD_SIZE) {
            game::hv_to_loc(h, v)
        } else {
            Loc::MAX
        }
    } else {
        Loc::MAX
    }
}

fn parse_ue_pointstring(text:&str) -> Vec<Loc> {
    let mut locs:Vec<Loc> = Vec::new();
    let loctexts:Vec<&str> = text.split_terminator(MESSAGE_SPLIT_POINTSTRING).collect();
    for loctext in loctexts {
        let loc = parse_ue_point(loctext);
        if loc < game::BOARD_LEN {
            locs.push(loc)
        }
    }
    locs
}

fn parse_ue_timelimit(text:&str) -> Duration {
    match text.parse::<u64>() {
        Ok(secs) => {
            let secs = TIMELIMIT_MAX_SECOND.min(TIMELIMIT_MIN_SECOND.max(secs));
            Duration::from_secs(secs)
        }
        _ => {Duration::from_secs(TIMELIMIT_MIN_SECOND)}
    }
}

fn dispatch<T: Receiver>(head:&str, rest:&[&str], receiver:&mut T){
    match head {
        UE_HEAD_GET => {
            if rest.len() > 1 {
                let me_piece = parse_ue_piece(rest[0]);
                let time_usable = parse_ue_timelimit(rest[1]);
                receiver.get(me_piece, time_usable, if rest.len() > 2 {rest[2]} else {""})
            }
        }
        UE_HEAD_SET_BOARD => {
            if rest.len() > 0 {
                let parts:Vec<&str> = rest[0].split_terminator(MESSAGE_SPLIT_1).collect();
                let black_locs:Vec<Loc> = if parts.len() > 0 {parse_ue_pointstring(parts[0])} else {Vec::new()};
                let white_locs:Vec<Loc> = if parts.len() > 1 {parse_ue_pointstring(parts[1])} else {Vec::new()};
                let lastmove_loc:Option<Loc> = if parts.len() > 2 {Some(parse_ue_point(parts[2]))} else {None};
                receiver.set_board(&black_locs, &white_locs, lastmove_loc)
            }
        }
        UE_HEAD_SET_RULE => {
            if rest.len() > 1 {
                let name = rest[0].to_lowercase();
                match name.as_str() {
                    "forbidden" => {
                        let v = rest[1].to_lowercase();
                        let use_forbidden = match v.as_str() {
                            "y" | "yes" | "t" | "true" => {true}
                            _ => {false}
                        };
                        receiver.set_rule_forbidden(use_forbidden)
                    }
                    "canswaps" => {
                        let insteps:Vec<usize> = rest[1].split_terminator(MESSAGE_SPLIT_1)
                            .map(|a|usize::from_str_radix(a, 10).unwrap_or(0))
                            .filter(|a| *a > 0)
                            .collect();
                        receiver.set_rule_canswaps(&insteps)
                    }
                    _ => {}
                }
            }
        }
        UE_HEAD_GIVE_POINTS => {
            if rest.len() > 1 {
                let point_nums = usize::from_str_radix(rest[0], 10).unwrap_or(1);
                let id = rest[1];
                receiver.give_points(point_nums, id)
            }
        }
        UE_HEAD_SELECT_POINT => {
            if rest.len() > 1 {
                let point_locs:Vec<Loc> = rest[0].split_terminator(MESSAGE_SPLIT_1)
                    .map(parse_ue_point)
                    .filter(|a| *a < game::BOARD_LEN)
                    .collect();
                let id = rest[1];
                receiver.select_point(&point_locs, id)
            }
        }
        UE_HEAD_SET_CONF => {
            if rest.len() > 1 {
                let id = rest[0];
                let raw_value = rest[1];
                receiver.set_conf(id, raw_value)
            }
        }
        UE_HEAD_RESET => {
            receiver.reset()
        }
        _ => {}
    }
}

pub enum Reply {
    MoveLoc(String, Loc),
    MoveSwap(String),
    MovePass(String),
    InPoints(Vec<Vec<Loc>>),
    Status(String),
    Say(String),
    Conf(String,String,String,String),
    Crash(String),
    Nothing,
}

// Processer Defeine for Engine->Ux
pub trait Sender {
    fn on_frame(&mut self) -> Reply;
}

fn encode_pointstring(locs:&Vec<Loc>) -> String {
    let loctexts:Vec<&str> = locs.iter().map(|loc| EU_LOC_TEXTS[*loc]).collect();
    loctexts.join(MESSAGE_SPLIT_POINTSTRING)
}

fn encode_pointstring_levels(levels:&Vec<Vec<Loc>>) -> String {
    let leveltexts:Vec<String> = levels.iter().map(encode_pointstring).collect();
    leveltexts.join(MESSAGE_SPLIT_1)
}

fn encode_slt(text:&str) -> String {
    let mut slt = String::new();
    slt.reserve(text.len());
    for a in text.chars() {
        match a {
            '\\' => {slt.push_str("\\\\")}
            '\'' => {slt.push_str("\\'")}
            '"' => {slt.push_str("\\\"")}
            ' ' => {slt.push_str("\\s")}
            _ => {
                if a > ' ' {
                    slt.push(a)
                }
            }
        }
    }
    slt
}

fn encode_mlt(text:&str) -> String {
    let mut mlt = String::new();
    mlt.reserve(text.len());
    for a in text.chars() {
        match a {
            '\\' => {mlt.push_str("\\\\")}
            '\'' => {mlt.push_str("\\'")}
            '"' => {mlt.push_str("\\\"")}
            ' ' => {mlt.push_str("\\s")}
            '\t' => {mlt.push_str("\\t")}
            '\n' => {mlt.push_str("\\n")}
            _ => {
                if a > ' ' {
                    mlt.push(a)
                }
            }
        }
    }
    mlt
}

fn encode(reply:&Reply) -> Option<String> {
    match reply{
        Reply::MoveLoc(id, loc) => {
            Some(format!("{} {} {}", EU_HEAD_MOVE, id, EU_LOC_TEXTS[*loc]))
        }
        Reply::MoveSwap(id) => {
            Some(format!("{} {} swap", EU_HEAD_MOVE, id))
        }
        Reply::MovePass(id) => {
            Some(format!("{} {} pass", EU_HEAD_MOVE, id))
        }
        Reply::InPoints(levels) => {
            Some(format!("{} {}", EU_HEAD_IN_POINTS, encode_pointstring_levels(levels)))
        }
        Reply::Status(text) => {
            Some(format!("{} {}", EU_HEAD_STATUS, encode_slt(text)))
        }
        Reply::Say(text) => {
            Some(format!("{} {}", EU_HEAD_SAY, encode_mlt(text)))
        }
        Reply::Conf(id, t, d, name) => {
            Some(format!("{} {} {} {} {}", EU_HEAD_CONF, id, t, d, name))
        }
        Reply::Crash(text) => {
            Some(format!("{} {}", EU_HEAD_CRASH, encode_mlt(text)))
        }
        Reply::Nothing => {None}
    }
}

fn send_to_stdout(line:String) {
    io::stdout().write(line.as_bytes()).unwrap();
    io::stdout().write(&[10u8]).unwrap();
    io::stdout().flush().unwrap();
}

pub fn session<T: Receiver+Sender>(engine_name:&str, processer:&mut T){
    send_to_stdout(format!("{} {}", EU_HEAD_READY, engine_name));
    //
    fn async_stdin_read() -> mpsc::Receiver<String> {
        let (tx, rx) = mpsc::channel::<String>();
        thread::spawn(move || loop {
            let mut line = String::new();
            io::stdin().read_line(&mut line).unwrap();
            tx.send(line).unwrap();
        });
        rx
    }
    //
    let stdin_channel = async_stdin_read();
    let mut clock_send = Instant::now();
    let mut clock_take = Instant::now();
    loop {
        // Step-1 Async Get Message
        match stdin_channel.try_recv() {
            Ok(line) => {
                send_to_stdout(format!("Take {}", line));
                let units:Vec<&str> = line.trim().split_terminator(MESSAGE_SPLIT_0).collect();
                if units.len() > 0 {
                    let head:String = units[0].to_uppercase();
                    match head.as_str() {
                        HEAD_HEARTBEAT => {
                            clock_take = Instant::now()
                        }
                        UE_HEAD_RAII => {
                            processer._raii();
                            return
                        }
                        _ => {
                            dispatch(&head,&units[1..units.len()], processer)
                        }
                    }
                }
            }
            Err(mpsc::TryRecvError::Empty) => {}
            Err(mpsc::TryRecvError::Disconnected) => {
                return
            }
        }
        // Step-2 Send Message
        let reply = processer.on_frame();
        match encode(&reply) {
            Some(code) => {
                send_to_stdout(code);
            }
            None => {}
        }
        // Step-3 Check Heartbeat Message from Ux
        if clock_take.elapsed() > HEARTBETA_TAKE_TIMEOUT {
            processer._raii();
            return
        }
        // Step-4 Process Heartbeat Message
        if clock_send.elapsed() > HEARTBEAT_SEND_TIMEOUT {
            send_to_stdout(String::from(HEAD_HEARTBEAT));
            clock_send = Instant::now()
        }
        // Step-5 Sleep
        thread::sleep(SESSION_FRAME_SPAC_TIME)
        // Next to Step-1
    }
}
